/**
 * $Id: PositionDao.java 6 2011-09-14 00:18:07Z adriftrock@gmail.com $
 */

package com.swehr.server.daos.impl;

import com.google.inject.Inject;
import com.hp.hpl.jena.query.QueryExecution;
import com.hp.hpl.jena.query.QuerySolution;
import com.hp.hpl.jena.query.ResultSet;
import com.hp.hpl.jena.rdf.model.Resource;
import com.swehr.server.daos.IPositionDao;
import com.swehr.server.models.Position;
import com.swehr.utils.StringUtil;
import org.apache.log4j.Logger;

import java.util.ArrayList;
import java.util.List;

/**
 * @author Rock Huang
 * @version 0.1
 */
public class PositionDao implements IPositionDao {
    private static Logger logger = Logger.getLogger(PositionDao.class);
    private InferenceDao inf;

    public PositionDao() {
    }

    @Inject
    public void setInf(InferenceDao inf) {
        this.inf = inf;
    }

    public List<Position> findByOrg(String orgId) {
        StringBuffer cmd = new StringBuffer(1024);
        cmd.append("SELECT * WHERE{ \n  ");
        cmd.append(Const.URI_ORG).append(" ehr:hasOrg ?sub . \n  ");
        cmd.append("?sub ehr:hasPosition ?pos . \n  ");
        cmd.append("?pos ehr:hasId ?id . \n  ");
        cmd.append("?t rdfs:subClassOf ehr:Position . \n  ");
        cmd.append("?pos a ?t .\n  ");
        cmd.append("?pos ehr:hasName ?nm . \n  ");
        cmd.append("OPTIONAL { ?pos ehr:isManagerialOf ?org } . \n  ");
        cmd.append("OPTIONAL { ?pos ehr:isTechnicalOf ?org } . \n");
        cmd.append("}");
        SparqlBuilder.replace(cmd, "@id", orgId);
        List<Position> list = new ArrayList<Position>();
        QueryExecution qe = null;
        try {
            qe = inf.create(cmd.toString());
            ResultSet rs = qe.execSelect();
            while (rs.hasNext()) {
                list.add(one(rs.nextSolution()));
            }
        } catch (Exception e) {
            logger.error("find(orgId), failed operation.", e);
        } finally {
            if (qe != null) {
                qe.close();
            }
        }

        return list;
    }

    public Position findById(int posId) {
        StringBuffer cmd = new StringBuffer(1024);
        ConnectDao conn = new ConnectDao();
        conn.setDefaultNS(cmd);
        cmd.append("SELECT * ");
        cmd.append("WHERE{ \n");
        cmd.append("   GRAPH ").append(Const.GRAPH_POS).append(" {\n");
        cmd.append("    ?pos ehr:hasId ?id . \n");
        cmd.append("    ?pos a ?t .\n");
        cmd.append("    ?pos ehr:hasName ?nm . \n");
        cmd.append("    OPTIONAL { ?pos ehr:isManagerialOf ?org } . \n");
        cmd.append("    OPTIONAL { ?pos ehr:isTechnicalOf ?org } . \n");
        cmd.append("    ?pos ehr:hasId ").append(posId).append("\n");
        cmd.append("   }\n}");
        Position pos = null;
        try {
            ResultSet rs = conn.executeQuery(cmd.toString());
            pos = one(rs.nextSolution());
        } catch (Exception e) {
            logger.error("findById(), failed Operation: " + e);
        } finally {
            conn.close();
        }
        return pos;
    }

    private Position one(QuerySolution qs) {
        Position pos = new Position();
        pos.setId(qs.getLiteral("?id").getInt());
        pos.setType(qs.getResource("?t").getLocalName());
        pos.setName(qs.getLiteral("?nm").getString());
        Resource r = qs.getResource("?org");
        pos.setRefOrg(r != null ? r.getURI() : null);
        return pos;
    }

    public boolean batchSave(List<Position> created) throws Exception {
        StringBuffer cmd = new StringBuffer();
        ConnectDao conn = new ConnectDao();
        conn.setDefaultNS(cmd);
        cmd.append("INSERT DATA {\n");
        cmd.append("  GRAPH ").append(Const.GRAPH_POS).append(" { \n");

        for (Position p : created) {
            if (p.getId() != 0) {
                throw new IllegalArgumentException("Position.ID should be 0");
            }
            cmd.append("    " + Const.URI_POS).append(" rdf:type ehr:").append(p.getType()).append(";\n");
            cmd.append("    ehr:hasId @id ; \n");
            cmd.append("    ehr:hasName \"").append(p.getName()).append("\" ; \n");
            if (p.getRefOrg() != null) {
                cmd.append("    ehr:is").append(p.getType()).append("Of ").append(SparqlBuilder.wrapOrg(p.getRefOrg())).append(" ; \n");
            }

            int id = IdGenerator.getInstance().getMaxPosId();
            SparqlBuilder.replace(cmd, "@id", String.valueOf(++id));
            SparqlBuilder.replace(cmd, "@id", String.valueOf(id));
            IdGenerator.getInstance().setMaxPosId(id);
            SparqlBuilder.replaceLast(cmd, ";", ".");
        }
        cmd.append("  }\n}");

        return conn.execUpdate(cmd.toString(), inf);
    }

    public boolean batchUpdate(List<Position> updated) {
        boolean change = false;
        StringBuffer cmd = new StringBuffer(1024);
        ConnectDao conn = new ConnectDao();
        conn.setDefaultNS(cmd);

        for (Position p : updated) {
            // if fields are empty, the sparql will be still generated.
            // This segment will avoid the case.
            if (StringUtil.isEmptyOrNull(p.getName(), p.getType(), p.getRefOrg())) {
                continue;
            }

            Position old = findById(p.getId());
            cmd.append("WITH ").append(Const.GRAPH_POS + "\n");
            cmd.append("DELETE { @t@org@nm } \n");
            cmd.append("INSERT { @t@org@nm } \n");
            cmd.append("WHERE {\n");
            cmd.append("  ?o a ehr:").append(old.getType()).append(" .\n");
            cmd.append("  ?o ehr:hasId @id");
            cmd.append("\n};\n");

            prepareUpdateSparql(old, p, cmd);
            change = true;
        }

        return !change || conn.execUpdate(cmd.toString(), inf);
    }

    private void prepareUpdateSparql(Position old, Position pos, StringBuffer cmd) {
        // check if the given Position object has no id value then return.
        if (pos.getId() <= 0) {
            throw new NullPointerException("Cannot found ID for Position update");
        } else {
            int p = cmd.indexOf("@id");
            cmd.replace(p, p + 3, String.valueOf(pos.getId()));
        }

        // compare with old one and prepare partial SPARQL
        boolean s = false; // if needs separator
        s = SparqlBuilder.replace(cmd, "@t", "rdf:type", old.getType(), pos.getType(),
                s, SparqlBuilder.isModified(pos.getType(), old.getType()));
        s = SparqlBuilder.replaceOrg(cmd, old.getType(), pos.getType(), old.getRefOrg(), pos.getRefOrg(), s);
        SparqlBuilder.replace(cmd, "@nm", "hasName", old.getName(),
                pos.getName(), s, SparqlBuilder.isModified(pos.getName(), old.getName()));
    }

    public boolean batchRemove(String[] removed) {
        StringBuffer cmd = new StringBuffer();
        ConnectDao conn = new ConnectDao();
        conn.setDefaultNS(cmd);
        for (String id : removed) {
            int i = Integer.parseInt(id);
            if (i <= 0) {
                throw new NullPointerException("Cannot found Position ID for RM");
            }
            cmd.append("WITH ").append(Const.GRAPH_POS + "\n");
            cmd.append("DELETE { ?o ?p ?v }\n");
            cmd.append("WHERE { \n");
            cmd.append("    ?o ?p ?v ; ehr:hasId ").append(i);
            cmd.append("\n};\n");
        }

        return conn.execUpdate(cmd.toString(), inf);
    }
}
